/****************************************************************************
 * Copyright (c) 2008, 2010 Andreas Unger and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Andreas Unger - initial API and implementation 
 ****************************************************************************/

package org.eclipselabs.damos.expressions.util;

import java.math.BigDecimal;
import java.math.BigInteger;

import org.eclipselabs.damos.common.math.DoubleUtil;
import org.eclipselabs.damos.dml.FixedPointDataType;

/**
 * @author Andreas Unger
 *
 */
public class FixedPointDataTypeUtil {
	
	private static BigInteger[] cachedScalingFactors;
	private static BigInteger[] cachedMinValuesSigned;
	private static BigInteger[] cachedMaxValuesSigned;
	private static BigInteger[] cachedMaxValuesUnsigned;
	
	public static BigInteger getScalingFactor(FixedPointDataType dataType) {
		int fractionalLength = dataType.getFractionalLength();
		if (cachedScalingFactors == null) {
			cachedScalingFactors = new BigInteger[128];
		}
		if (fractionalLength < cachedScalingFactors.length) {
			BigInteger scalingFactor = cachedScalingFactors[fractionalLength];
			if (scalingFactor == null) {
				scalingFactor = computeScalingFactor(fractionalLength);
				cachedScalingFactors[fractionalLength] = scalingFactor;
			}
			return scalingFactor;
		}
		return computeScalingFactor(fractionalLength);
	}
	
	private static BigInteger computeScalingFactor(int fractionalLength) {
		return BigInteger.ONE.shiftLeft(fractionalLength);
	}
	
	public static BigInteger getMinValue(FixedPointDataType dataType) {
		int wordLength = dataType.getWordLength();
		if (dataType.isSigned()) {
			if (cachedMinValuesSigned == null) {
				cachedMinValuesSigned = new BigInteger[128];
			}
			if (wordLength < cachedMinValuesSigned.length) {
				BigInteger minValue = cachedMinValuesSigned[wordLength];
				if (minValue == null) {
					minValue = computeMinValueSigned(wordLength);
					cachedMinValuesSigned[wordLength] = minValue;
				}
				return minValue;
			}
			return computeMinValueSigned(wordLength);
		}
		return BigInteger.ZERO;
	}
	
	private static BigInteger computeMinValueSigned(int wordLength) {
		return BigInteger.ONE.shiftLeft(wordLength - 1).negate();
	}

	public static BigInteger getMaxValue(FixedPointDataType dataType) {
		int wordLength = dataType.getWordLength();

		if (dataType.isSigned()) {
			if (cachedMaxValuesSigned == null) {
				cachedMaxValuesSigned = new BigInteger[128];
			}
			if (wordLength < cachedMaxValuesSigned.length) {
				BigInteger maxValue = cachedMaxValuesSigned[wordLength];
				if (maxValue == null) {
					maxValue = computeMaxValueSigned(wordLength);
					cachedMaxValuesSigned[wordLength] = maxValue;
				}
				return maxValue;
			}
			return computeMaxValueSigned(wordLength);
		}

		if (cachedMaxValuesUnsigned == null) {
			cachedMaxValuesUnsigned = new BigInteger[128];
		}
		if (wordLength < cachedMaxValuesUnsigned.length) {
			BigInteger maxValue = cachedMaxValuesUnsigned[wordLength];
			if (maxValue == null) {
				maxValue = computeMaxValueUnsigned(wordLength);
				cachedMaxValuesUnsigned[wordLength] = maxValue;
			}
			return maxValue;
		}
		return computeMaxValueUnsigned(wordLength);
	}
	
	private static BigInteger computeMaxValueSigned(int wordLength) {
		return BigInteger.ONE.shiftLeft(wordLength - 1).subtract(BigInteger.ONE);
	}

	private static BigInteger computeMaxValueUnsigned(int wordLength) {
		return BigInteger.ONE.shiftLeft(wordLength).subtract(BigInteger.ONE);
	}

	public static BigInteger toFixedPoint(double value, FixedPointDataType dataType) {
		BigInteger fractionalPart = DoubleUtil.getFractionPart(value).multiply(new BigDecimal(getScalingFactor(dataType))).toBigInteger();
		BigInteger maxFractionalPart = getScalingFactor(dataType).subtract(BigInteger.ONE);
		if (fractionalPart.compareTo(maxFractionalPart) > 0) {
			fractionalPart = maxFractionalPart;
		}
		return DoubleUtil.toBigInteger(value).shiftLeft(dataType.getFractionalLength()).or(fractionalPart);
	}

}
